
Alkaline.Processing.Operation = function(operation, condition) {
	this.operation = operation;
	this.arity = arguments.length-2;
	this.condition = condition;
	this.operands = [];
	for (var i = 2; i < arguments.length; i++)
		this.operands.push(arguments[i].getValue(condition));
	this.result = Alkaline.Processing.Operation.RESULT_NOT_COMPUTED;
}
Alkaline.Processing.Operation.RESULT_NOT_COMPUTED = {};
Alkaline.Processing.Operation.prototype.toString = function() {
	var s = '(' + this.operation;
	for (var i = 0; i < this.operands.length; i++)
		s += ' ' + this.operands[i];
	s += ')';
	return s;
}
Alkaline.Processing.Operation.prototype.getResult = function() {
	var p = Alkaline.Processing;
	if (this.result === p.Operation.RESULT_NOT_COMPUTED) {
		switch (this.operation) {
			case "+":
				if (this.operands.length === 1) {
					this.result = p.Operation.applyPositive(this.operands[0]);
					break;
				}
				// 2 operands
				this.result = p.Operation.applyAdd(this.operands[0], this.operands[1]);
				break;
			case "-":
				if (this.operands.length === 1) {
					this.result = p.Operation.applyNegative(this.operands[0]);
					break;
				}
				// 2 operands
				this.result = p.Operation.applySubstract(this.operands[0], this.operands[1]);
				break;
			case "*":
				this.result = p.Operation.applyMultiply(this.operands[0], this.operands[1]);
				break;
			case "/":
				this.result = p.Operation.applyDivision(this.operands[0], this.operands[1]);
				break;
			case "%":
				this.result = p.Operation.applyModulus(this.operands[0], this.operands[1]);
				break;
			case "===":
			case "!==":
			case "==":
			case "!=":
			case "<<":
			case ">>":
			case ">>>":
			case "?":
				break;
			case "!":
				this.result = p.Operation.applyNot(this.operands[0]);
				break;
			case "|":
			case "&":
			case "^":
			case "||":
			case "&&":
		}
		if (this.result === p.Operation.RESULT_NOT_COMPUTED) {
			// should not be reached when implementation is complete
			var value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.UNKNOWN_NATIVE);
			this.result = new p.ValueDistribution([{ condition: this.condition, value: value }]);
		}
	}
	return this.result;
}

Alkaline.Processing.Operation.applyNot = function(valueDistribution) {
	var p = Alkaline.Processing;
	var result = new p.ValueDistribution(valueDistribution);
	// apply the negation to each possible value
	for (var i = 0; i < result.values.length; i++) {
		var newVal = null;
		switch (result.values[i].valueType) {
			case p.ReferencedValue.VALUE_TYPE.FUNCTION:
			case p.ReferencedValue.VALUE_TYPE.OBJECT:
			case p.ReferencedValue.VALUE_TYPE.ARRAY:
			case p.ReferencedValue.VALUE_TYPE.REGEXP:
				newVal = false;
				break;
			case p.ReferencedValue.VALUE_TYPE.NUMBER:
				if (result.values[i].value !== null)
					newVal = !result.values[i].value;
				break;
			case p.ReferencedValue.VALUE_TYPE.STRING:
				if (result.values[i].value !== null)
					newVal = !result.values[i].value;
				break;
			case p.ReferencedValue.VALUE_TYPE.BOOLEAN:
				if (result.values[i].value !== null)
					newVal = !result.values[i].value;
				break;
			case p.ReferencedValue.VALUE_TYPE.NULL:
			case p.ReferencedValue.VALUE_TYPE.UNDEFINED:
				newVal = true;
				break;
			case p.ReferencedValue.VALUE_TYPE.UNKNOWN:
			case p.ReferencedValue.VALUE_TYPE.UNKNOWN_NATIVE:
				break;
			case p.ReferencedValue.VALUE_TYPE.ARGUMENTS_ARRAY:
				newVal = false;
				break;
		}
		if (newVal)
			newVal = p.ReferencedValue.primitives.True;
		else if (newVal === false)
			newVal = p.ReferencedValue.primitives.False;
		else
			newVal = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.BOOLEAN, null);
		result.values[i] = newVal;
	}
	return result;
}

Alkaline.Processing.Operation.applyAdd = function(value1, value2) {
	// value1 and value2 are actually valueDistributions
	var p = Alkaline.Processing;
	var conditions = [], values = [];
	// special values:
	var unknown = {}, undef;
	
	// apply the addition to each possible value from each valueDistribution
	for (var i = 0; i < value1.values.length; i++)
		for (var j = 0; j < value2.values.length; j++) {
			var condition = value1.conditions[i].and(value2.conditions[j]);
			if (condition === p.Condition.NEVER)
				continue;
			var value = null;
			var lhs = value1.values[i]._toPrimitive(unknown), rhs = value2.values[j]._toPrimitive(unknown);
			var isString = typeof lhs === 'string' || typeof rhs === 'string';
			if (lhs === unknown || rhs === unknown) {
				if (isString)
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.STRING, null);
				else
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.UNKNOWN_NATIVE, null);
			} else if (lhs === rhs && (rhs === undef || rhs === null))
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, 0);
			else
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE[isString ? 'STRING' : 'NUMBER'], lhs + rhs);
			conditions.push(condition);
			values.push(value);
		}
	return new p.ValueDistribution({ conditions: conditions, values: values });
}

Alkaline.Processing.Operation.applyPositive = function(valueDistribution) {
	var p = Alkaline.Processing;
	var result = new p.ValueDistribution(valueDistribution);
	// apply the negation to each possible value
	for (var i = 0; i < result.values.length; i++)
		result.values[i] = result.values[i].toNumber();
	return result;
}

Alkaline.Processing.Operation.applySubstract = function(value1, value2) {
	// value1 and value2 are actually valueDistributions
	var p = Alkaline.Processing;
	var conditions = [], values = [];
	// special values:
	var unknown = {};
	
	// apply the addition to each possible value from each valueDistribution
	for (var i = 0; i < value1.values.length; i++)
		for (var j = 0; j < value2.values.length; j++) {
			var condition = value1.conditions[i].and(value2.conditions[j]);
			if (condition === p.Condition.NEVER)
				continue;
			var value = null;
			var lhs = value1.values[i]._toPrimitive(unknown), rhs = value2.values[j]._toPrimitive(unknown);
			if (lhs === unknown || rhs === unknown) {
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, null);
			} else
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, lhs - rhs);
			conditions.push(condition);
			values.push(value);
		}
	return new p.ValueDistribution({ conditions: conditions, values: values });
}

Alkaline.Processing.Operation.applyNegative = function(valueDistribution) {
	var p = Alkaline.Processing;
	var NUMBER = p.ReferencedValue.VALUE_TYPE.NUMBER;
	var result = new p.ValueDistribution(valueDistribution);
	// apply the negation to each possible value
	for (var i = 0; i < result.values.length; i++) {
		result.values[i] = result.values[i].toNumber(true);
		if (result.values[i].value !== null)
			result.values[i].value = -result.values[i].value;
	}
	return result;
}

Alkaline.Processing.Operation.applyMultiply = function(value1, value2) {
	// value1 and value2 are actually valueDistributions
	var p = Alkaline.Processing;
	var conditions = [], values = [];
	// special values:
	var unknown = {};
	
	// apply the addition to each possible value from each valueDistribution
	for (var i = 0; i < value1.values.length; i++)
		for (var j = 0; j < value2.values.length; j++) {
			var condition = value1.conditions[i].and(value2.conditions[j]);
			if (condition === p.Condition.NEVER)
				continue;
			var value = null;
			var lhs = value1.values[i]._toPrimitive(unknown), rhs = value2.values[j]._toPrimitive(unknown);
			if (lhs === unknown || rhs === unknown) {
				if ((typeof lhs === 'number' && isNaN(lhs)) || (typeof rhs === 'number' && isNaN(rhs)))
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, NaN);
				else
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, null);
			} else
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, lhs * rhs);
			conditions.push(condition);
			values.push(value);
		}
	return new p.ValueDistribution({ conditions: conditions, values: values });
}

Alkaline.Processing.Operation.applyDivision = function(value1, value2) {
	// value1 and value2 are actually valueDistributions
	var p = Alkaline.Processing;
	var conditions = [], values = [];
	// special values:
	var unknown = {};
	
	// apply the addition to each possible value from each valueDistribution
	for (var i = 0; i < value1.values.length; i++)
		for (var j = 0; j < value2.values.length; j++) {
			var condition = value1.conditions[i].and(value2.conditions[j]);
			if (condition === p.Condition.NEVER)
				continue;
			var value = null;
			var lhs = value1.values[i]._toPrimitive(unknown), rhs = value2.values[j]._toPrimitive(unknown);
			if (lhs === unknown || rhs === unknown) {
				if ((typeof lhs === 'number' && isNaN(lhs)) || (typeof rhs === 'number' && isNaN(rhs)))
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, NaN);
				else
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, null);
			} else
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, lhs / rhs);
			conditions.push(condition);
			values.push(value);
		}
	return new p.ValueDistribution({ conditions: conditions, values: values });
}

Alkaline.Processing.Operation.applyModulus = function(value1, value2) {
	// value1 and value2 are actually valueDistributions
	var p = Alkaline.Processing;
	var conditions = [], values = [];
	// special values:
	var unknown = {};
	
	// apply the addition to each possible value from each valueDistribution
	for (var i = 0; i < value1.values.length; i++)
		for (var j = 0; j < value2.values.length; j++) {
			var condition = value1.conditions[i].and(value2.conditions[j]);
			if (condition === p.Condition.NEVER)
				continue;
			var value = null;
			var lhs = value1.values[i]._toPrimitive(unknown), rhs = value2.values[j]._toPrimitive(unknown);
			if (lhs === unknown || rhs === unknown) {
				if ((typeof lhs === 'number' && isNaN(lhs)) || (typeof rhs === 'number' && isNaN(rhs)))
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, NaN);
				else
					value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, null);
			} else
				value = new p.ReferencedValue(p.ReferencedValue.VALUE_TYPE.NUMBER, lhs % rhs);
			conditions.push(condition);
			values.push(value);
		}
	return new p.ValueDistribution({ conditions: conditions, values: values });
}
